package com.bolt.report.util;

import com.bolt.common.convert.Convert;
import com.bolt.common.utils.StrUtil;
import com.bolt.report.exception.ReportException;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public class GoogleBarCodeUtil {
    private final static Logger logger = LoggerFactory.getLogger(GoogleBarCodeUtil.class);
    private static final int BARCODEHEIGHT = 128;
    private static final int BARCODEWIDTH = 128;
    private static final String TEXTPOSITION = "bottom";
    private static final String TEXTALIGN = "center";

    public static String createCode(final String codeContent, String type, Map<String, Object> option) {
        try {

            Map<EncodeHintType, Object> hints = new HashMap();
            hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
            hints.put(EncodeHintType.MARGIN, 0);

            Optional<Map<String, Object>> optionOf = Optional.ofNullable(option);
            int width = optionOf.map(k -> Convert.toInt(k.get("width"), BARCODEWIDTH)).orElse(BARCODEWIDTH);
            int height = optionOf.map(k -> Convert.toInt(k.get("height"), BARCODEHEIGHT)).orElse(BARCODEHEIGHT);
            Color frontColor = Color.BLACK;
            if (optionOf.isPresent()) {
                if (null != option.get("frontColor")) {
                    frontColor = Color.decode((String) option.get("frontColor"));
                }
            }
            Color backgroundColor = Color.WHITE;
            if (optionOf.isPresent()) {
                if (null != option.get("backgroundColor")) {
                    backgroundColor = Color.decode((String) option.get("backgroundColor"));
                }
            }
            int fontOption = optionOf.map(k -> Convert.toInt(k.get("fontOption"), 0)).orElse(0);
            int fontSize = optionOf.map(k -> Convert.toInt(k.get("fontSize"), 20)).orElse(20);
            String textPosition = optionOf.map(k -> Convert.toStr(k.get("textPosition"), TEXTPOSITION)).orElse(TEXTPOSITION);
            String textAlign = optionOf.map(k -> Convert.toStr(k.get("textAlign"), TEXTALIGN)).orElse(TEXTALIGN);
            boolean displayValue = optionOf.map(k -> Convert.toBool(k.get("displayValue"), false)).orElse(false);
            String text = optionOf.map(k -> Convert.toStr(k.get("text"), codeContent)).orElse(codeContent);
            /**
             * MultiFormatWriter:多格式写入，这是一个工厂类，里面重载了两个 encode 方法，用于写入条形码或二维码
             *      encode(String contents,BarcodeFormat format,int width, int height,Map<EncodeHintType,?> hints)
             *      contents:条形码/二维码内容
             *      format：编码类型，如 条形码，二维码 等
             *      width：码的宽度
             *      height：码的高度
             *      hints：码内容的编码类型
             * BarcodeFormat：枚举该程序包已知的条形码格式，即创建何种码，如 1 维的条形码，2 维的二维码 等
             * BitMatrix：位(比特)矩阵或叫2D矩阵，也就是需要的二维码
             */

            BarcodeFormat format = BarcodeFormat.QR_CODE;
            if (StrUtil.equalsIgnoreCase(type, "BARCODE")) {
                format = BarcodeFormat.CODE_128;
            }
            MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
            BitMatrix bitMatrix = multiFormatWriter.encode(codeContent, format, width, height, hints);
            /**java.awt.image.BufferedImage：具有图像数据的可访问缓冲图像，实现了 RenderedImage 接口
             * BitMatrix 的 get(int x, int y) 获取比特矩阵内容，指定位置有值，则返回true，将其设置为前景色，否则设置为背景色
             * BufferedImage 的 setRGB(int x, int y, int rgb) 方法设置图像像素
             *      x：像素位置的横坐标，即列
             *      y：像素位置的纵坐标，即行
             *      rgb：像素的值，采用 16 进制,如 0xFFFFFF 白色
             */
            BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
            for (int x = 0; x < width; x++) {
                for (int y = 0; y < height; y++) {
                    bufferedImage.setRGB(x, y, bitMatrix.get(x, y) ? frontColor.getRGB() : backgroundColor.getRGB());
                }
            }
            if (StrUtil.equalsIgnoreCase(type, "barcode") && displayValue) {
                insertWords(bufferedImage, text, fontSize, frontColor, backgroundColor, fontOption, textPosition, textAlign);
            }
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ImageIO.write(bufferedImage, "png", outputStream);

            return "data:image/png;base64," + Base64.getEncoder().encodeToString(outputStream.toByteArray());

        } catch (Exception ex) {
            logger.error("createCode error", ex);
            throw new ReportException(ex);
        }

    }


    public static void insertWords(BufferedImage image, String words, int fontSize, Color fontColor, Color backgroundColor, int fontOption, String textPosition, String textAlign) {
        // 新的图片，把带logo的二维码下面加上文字
        if (StrUtil.isNotEmpty(words)) {
            Graphics2D graphics = image.createGraphics();
            setGraphics2D(graphics);
            graphics.drawImage(image, 0, 0, null);
            int fontStyle;
            if (fontOption == 1) {
                fontStyle = Font.BOLD;
            } else if (fontOption == 2) {
                fontStyle = Font.ITALIC;
            } else if (fontOption == 3) {
                fontStyle = Font.BOLD + Font.ITALIC;
            } else {
                fontStyle = Font.PLAIN;
            }
            Font f = new Font("Arial", fontStyle, fontSize);

            FontRenderContext frc = image.getGraphics().getFontMetrics().getFontRenderContext();
            Rectangle2D rect = f.getStringBounds(words, frc);
            graphics.setColor(backgroundColor);

            //add 10 pixels to width to get 5 pixels of padding in left/right
            //add 6 pixels to height to get 3 pixels of padding in top/bottom
            int fillX = 0, fillY = 0, drawX = 0, drawY = 0;
            if ("top".equals(textPosition)) {
                fillX = 0;
                fillY = 0;
                drawY = (int) Math.ceil(rect.getHeight());
            } else if ("bottom".equals(textPosition)) {
                fillX = 0;
                fillY = (int) Math.ceil(image.getHeight() - (rect.getHeight() + 6));
                drawY = (int) Math.ceil(image.getHeight() - 6);
            }
            if ("center".equals(textAlign)) {
                drawX = (int) Math.ceil((image.getWidth() / 2) - ((rect.getWidth()) / 2));
            } else if ("left".equals(textAlign)) {
                drawX = 0;
            } else if ("right".equals(textAlign)) {
                drawX = (int) Math.ceil(image.getWidth() - rect.getWidth());
            }
            graphics.fillRect(
                    fillX,
                    fillY,
                    (int) Math.ceil(image.getWidth()),
                    (int) Math.ceil(rect.getHeight() + 6));
            // add the watermark text
            graphics.setFont(f);
            graphics.setColor(fontColor);
            graphics.drawString(words, drawX, drawY);
            graphics.dispose();

        }
    }

    /**
     * 设置 Graphics2D 属性  （抗锯齿）
     *
     * @param g2d Graphics2D提供对几何形状、坐标转换、颜色管理和文本布局更为复杂的控制
     */
    private static void setGraphics2D(Graphics2D g2d) {
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_DEFAULT);
        Stroke s = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER);
        g2d.setStroke(s);
    }

}


